package gpl.scotlandyard.beans.basics;

import java.util.LinkedList;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.log4j.Logger;

/** @author Norbert Martin */
public class Player implements Cloneable {

  private static final Logger LOG = Logger.getLogger(Player.class);

  private final ConcurrentLinkedQueue<Player.Listener> listeners = new ConcurrentLinkedQueue<Player.Listener>();

  private int bus;
  private boolean doDoubleMove;
  private int hide;
  private boolean isFugitive;
  private boolean isNextMoveHidden;
  private String name;
  private Node node = Node.getUnknownNode();
  private LinkedList<Link> prevMoves = new LinkedList<Link>();
  private boolean proceedDoubleMove;
  private int taxi;
  private int twice;
  private int ug;

  /** Creates player. */
  public Player() {
  }

  /** Creates player.
   * @param name */
  public Player(String name) {
    this.name = name;
  }

  /** Creates player.
   * @param name
   * @param taxi
   * @param bus
   * @param ug */
  public Player(String name, int taxi, int bus, int ug) {
    this(name, taxi, bus, ug, 0, 0);
  }

  /** Creates player.
   * @param name
   * @param taxi
   * @param bus
   * @param ug
   * @param hide
   * @param twice */
  public Player(String name, int taxi, int bus, int ug, int hide, int twice) {
    this.name = name;
    this.taxi = taxi;
    this.bus = bus;
    this.ug = ug;
    this.hide = hide;
    this.twice = twice;
  }

  public Player(String name, boolean isFugitive) {
    this(name);
    this.isFugitive = isFugitive;
  }

  /** @param link */
  public final void addPreviousMove(Link link) {
    prevMoves.add(link);
  }

  /** @return the bus */
  public final int getBus() {
    return bus;
  }

  /** @return the hide */
  public final int getHide() {
    return hide;
  }

  /** Returns the last used ticket, replaces hide by the real type.
   * @return last used ticket */
  public final Ticket getLastUsedTicket() {
    Link l = prevMoves.get(prevMoves.size() - 1);
    if (l.getTicket() == Ticket.HIDE) {
      return l.getHiddenTicket();
    }
    return l.getTicket();
  }

  /** @return the name */
  public final String getName() {
    return name;
  }

  /** @return the node */
  public final Node getNode() {
    return node;
  }

  /** @return the prevMoves */
  public final LinkedList<Link> getPrevMoves() {
    return prevMoves;
  }

  /** @return the taxi */
  public final int getTaxi() {
    return taxi;
  }

  /** @return the twice */
  public final int getTwice() {
    return twice;
  }

  /** @return the ug */
  public final int getUg() {
    return ug;
  }

  /** @return the doDoubleMove */
  public final boolean isDoDoubleMove() {
    return doDoubleMove;
  }

  /** @return the isFugitive */
  public final boolean isFugitive() {
    return isFugitive;
  }

  /** @return the isNextMoveHidden */
  public final boolean isNextMoveHidden() {
    return isNextMoveHidden;
  }

  /** @return the proceedDoubleMove */
  public final boolean isProceedDoubleMove() {
    return proceedDoubleMove;
  }

  /** @param bus the bus to set */
  public final void setBus(int bus) {
    this.bus = bus;
    firePlayerTicketsUpdated(this);
  }

  /** @param doDoubleMove the doDoubleMove to set */
  public final void setDoDoubleMove(boolean doDoubleMove) {
    if (doDoubleMove && twice == 0) {
      throw new IllegalStateException("can't do double move no ticket");
      // if you encounter this error see to implements this logic on your side
    }
    if (doDoubleMove && proceedDoubleMove) {
      throw new IllegalStateException("already doing a double move, can't add");
      // if you encounter this error see to implements this logic on your side
    }
    if (doDoubleMove) {
      proceedDoubleMove = false;
    }
    this.doDoubleMove = doDoubleMove;
  }

  /** @param isFugitive the isFugitive to set */
  public final void setFugitive(boolean isFugitive) {
    this.isFugitive = isFugitive;
  }

  /** @param hide the hide to set */
  public final void setHide(int hide) {
    this.hide = hide;
    firePlayerTicketsUpdated(this);
  }

  /** @param name the name to set */
  public final void setName(String name) {
    this.name = name;
  }

  /** @param isNextMoveHidden the isNextMoveHidden to set */
  public final void setNextMoveHidden(boolean isNextMoveHidden) {
    if (isNextMoveHidden && hide == 0) {
      throw new IllegalStateException("can't do hidden move no ticket");
      // if you encounter this error see to implements this logic on your side
    }
    this.isNextMoveHidden = isNextMoveHidden;
  }

  /** Warning : this method doesn't fire move, see {@link #moveTo(Node)}.
   * @param node the node to set */
  public final void setNode(Node node) {
    this.node = node;
  }

  /** Adds link to previous move and fires player move with this link.
   * @param link */
  public final void moveTo(Link link) {
    if (!link.getFromNode().equals(node)) {
      throw new IllegalStateException("try to move from : " + link.getFromNode() + " but is on : " + node);
    }
    this.node = link.getToNode();
    prevMoves.add(link);
    firePlayerMoved(this, getPrevMoves().getLast());
  }

  /** @param proceedDoubleMove the proceedDoubleMove to set */
  public final void setProceedDoubleMove(boolean proceedDoubleMove) {
    this.proceedDoubleMove = proceedDoubleMove;
  }

  /** @param taxi the taxi to set */
  public final void setTaxi(int taxi) {
    this.taxi = taxi;
    firePlayerTicketsUpdated(this);
  }

  /** @param twice the twice to set */
  public final void setTwice(int twice) {
    this.twice = twice;
    firePlayerTicketsUpdated(this);
  }

  /** @param ug the ug to set */
  public final void setUg(int ug) {
    this.ug = ug;
    firePlayerTicketsUpdated(this);
  }

  /** Not a complete clone, only name and node. Node is note a clone.
   * @see java.lang.Object#clone()
   */
  @Override
  public Player clone() {
    Player p = new Player();
    p.setName(name);
    p.setNode(node);
    return p;
  };

  @Override
  public final String toString() {
    return name + (isFugitive ? "[fugitive]" : "[detective]") + " - " + node + " [T= " + taxi + ", B=" + bus + ", U=" + ug + ", H=" + hide
        + ", 2x=" + twice + "]";
  }

  /** @author norbert */
  public static interface Listener {
    /** Called when the player location changed.
     * @param player impacted
     * @param link followed */
    void playerMoved(Player player, Link link);

    /** Called when the amounts of tickets of this player is updated.
     * @param player impacted */
    void playerTicketsUpdated(Player player);
  }

  /** @author norbert */
  public static class ListenerImpl implements Listener {
    @Override
    public void playerMoved(Player player, Link link) {
      // does nothing
    }

    @Override
    public void playerTicketsUpdated(Player player) {
      // does nothing
    }
  }

  /** hide or twice
   * @return */
  public final boolean hasSpecialTickets() {
    return hide > 0 || twice > 0;
  }

  /** Adds listener.
   * @param listener to add */
  public final void addListener(Player.Listener listener) {
    listener.getClass();
    listeners.add(listener);
  }

  /** Removes listener if listening this model.
   * @param listener to remove */
  public final void removeListener(Player.Listener listener) {
    listener.getClass();
    listeners.remove(listener);
  }

  /** Fires playerMoved
   * @param player
   * @param link */
  protected final void firePlayerMoved(Player player, Link link) {
    LOG.debug("firePlayerPositionUpdated : " + player);
    for (Player.Listener l : listeners) {
      l.playerMoved(player, link);
    }
  }

  /** Fires playerTicketsUpdated.
   * @param player */
  protected final void firePlayerTicketsUpdated(Player player) {
    LOG.debug("firePlayerTicketsUpdated : " + player);
    for (Player.Listener l : listeners) {
      l.playerTicketsUpdated(player);
    }
  }

}
